-
Notifications
You must be signed in to change notification settings - Fork 1
[๐BUG] QA ์๋ฆฌ์ฆ #331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough์ฒดํฌ๋ฆฌ์คํธ์ ๋นํธ์ง ํญ๋ชฉ์์ AddIcon ๋ฐฐ์ง๋ฅผ ์ ๊ฑฐํ๊ณ , ์ปค๋ฎค๋ํฐ HOT ๋ฆฌ์คํธ์ ์ด๋ฏธ์ง ํด๋ฐฑ ๋ฐ ํญ๋ชฉ ํด๋ฆญ ๋ค๋น๊ฒ์ด์ ์ ์ถ๊ฐํ์ต๋๋ค. ๋ฆฌํฌ๋ฃจํธ ์นด๋์ ๋ชจ๋ฌ ์์ธ๋ณด๊ธฐ์ ์ ํ/๋ซ๊ธฐ ์ํ๋ฅผ ๋์ ํ๊ณ , ํ ์ปดํฌ๋ํธ์ ํ ์ผ ์ถ๊ฐ ํธ๋ํน(Amplitude)๊ณผ ๋ถ๋งํฌ ํ ๊ธยทํ ์คํธ๋ฅผ ์ถ๊ฐํ์ต๋๋ค. ํ์ ๋ฐ ์คํค๋ง์ ์๊ท๋ชจ ํ๋ ๋ณ๊ฒฝ์ด ์์ต๋๋ค. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant HomeRecruit as HomeRecruit
participant Modal as CardDetail
User->>HomeRecruit: ๋ฆฌํฌ๋ฃจํธ ์นด๋ ํด๋ฆญ
HomeRecruit->>HomeRecruit: convertToRecruitItem(data)
HomeRecruit->>HomeRecruit: selectedCard ์ค์ , isModalOpen=true
HomeRecruit->>Modal: CardDetail ๋ ๋(item, onClose)
User-->>Modal: ๋ชจ๋ฌ ๋ซ๊ธฐ
Modal-->>HomeRecruit: onClose()
HomeRecruit->>HomeRecruit: isModalOpen=false
note right of HomeRecruit: ์ข์์ ์์ญ์ stopPropagation์ผ๋ก ์นด๋ ์ด๋ฆผ ๋ฐฉ์ง
sequenceDiagram
autonumber
actor User
participant Community as CommunityContents / OtherTodoCard
participant API as Add/Delete ๋ฎคํ
์ด์
participant Amplitude as trackTodoImport
participant Toast as ToastModal
User->>Community: "๋ด ํ ์ผ์ ์ถ๊ฐ" ํด๋ฆญ (todoTitle)
alt not added
Community->>Amplitude: trackTodoImport(todoTitle)
Community->>API: addTodo(id)
API-->>Community: success / error
else already added
Community->>API: deleteTodo(id)
API-->>Community: success / error
end
alt success
Community->>Community: ๋ก์ปฌ ์ํ/์นด์ดํธ ์
๋ฐ์ดํธ
Community->>Toast: show(๋ฉ์์ง)
else error
Community->>User: alert(์ค๋ฅ)
end
Estimated code review effort๐ฏ 4 (Complex) | โฑ๏ธ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
โจ Finishing Touches
๐งช Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and canโt be posted inline due to platform limitations.
โ ๏ธ Outside diff range comments (1)
src/hook/useHomeQuery.ts (1)
50-59: RecruitProps.url ํ๋ ์ฒ๋ฆฌ ๊ฐํ ํ์
HomeRecruit.tsx์convertToRecruitItem(49โ57ํ)์์data.url || ''๋ก ๋น ๋ฌธ์์ด ๋์ฒด๋ง ํ๊ณ ์์ด, ๋ฐฑ์๋๊ฐ url์ ๋๋ฝํ๋ฉด ๋น ๋งํฌ๊ฐ ๋ ธ์ถ๋ ์ ์์ต๋๋ค.
- ์ต์ A:
src/hook/useHomeQuery.ts์RecruitProps์์url?: string์ผ๋ก ๋ณ๊ฒฝํ๊ณ , ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ์์ ์์ ํ๊ฒ ์ฒ๋ฆฌ.- ์ต์ B(๊ถ์ฅ): zod ์คํค๋ง(
RecruitItemSchema)๋ก ์๋ต์ ๋ฐํ์ ๊ฒ์ฆํ๊ณ , ๋๋ฝ ์ ๋ช ์์ fallback(์๋ฌ ์ฒ๋ฆฌ ๋๋ ๊ธฐ๋ณธ URL) ์ ์ฉ.
๐งน Nitpick comments (13)
src/pages/home/components/HomeDreamer.tsx (1)
41-47: ์ ๊ทผ์ฑ ๊ฐ์ :<div onClick>๋์<Link>๋๋<button>์ฌ์ฉ ๊ถ์ฅ
src/common/Header.tsx(26)์์ โ/communityโ ๊ฒฝ๋ก๊ฐ ์ด๋ฏธ ์ ์๋์ด ์์ด ์๋ํ ๋ชฉ์ ์ง์ ์ผ์นํฉ๋๋ค; HomeDreamer.tsx(43)์์ ํด๋ฆญ ๊ฐ๋ฅํ ์์๋ฅผ<Link to="/community">๋๋<button>์ผ๋ก ๊ต์ฒดํ์ธ์.src/common/CheckList.tsx (1)
75-86: Amplitude ์ด๋ฒคํธ๋ช ๊ณผ ๋ก๊ทธ ๋ฉ์์ง ๋ถ์ผ์นtrack์ 'todo_create'๋ก ๋ณด๋ด๋๋ฐ, console.log๋ 'todo_create_attempt'๋ก ๊ธฐ์ฌ๋์ด ์์ต๋๋ค. ๋๋ฒ๊น ํผ์ ์ ์ค์ด๋ ค๋ฉด ์ผ์น์ํค๋ ๊ฒ์ด ์ข์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์์ ์ ์๋๋ฆฝ๋๋ค.
- console.log('Amplitude event sent: todo_create_attempt (inpage)'); + console.log('Amplitude event sent: todo_create (inpage)');src/pages/community/components/CommunityLeftSide.tsx (3)
56-59: onError ํด๋ฐฑ ์ฒ๋ฆฌ ์ ์src์ falsy๊ฐ ์๋ ์๋ชป๋ URL์ด ์ฌ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด onError๋ก ํด๋ฐฑ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ํ๋ ํธ์ด ์์ ํฉ๋๋ค.
- <img - src={item.imageUrl || BaseImage} + <img + src={item.imageUrl || BaseImage} + onError={(e) => ((e.currentTarget.src = BaseImage))} alt="ํ๋กํ์ด๋ฏธ์ง"
63-67: ๋ง์ค์ ํ ์คํธ ํํธ ์ ๊ณตtruncate ์ ์ฉ์ ์ข์ต๋๋ค. ์ ์ฒด ๋ด์ฉ์ title ์์ฑ์ผ๋ก ๋ ธ์ถํ๋ฉด ์ ๊ทผ์ฑ๊ณผ ๊ฐ๋ ์ฑ์ด ๊ฐ์ ๋ฉ๋๋ค.
- <div className="w-[170px] min-w-0 flex-1 truncate text-black font-B01-SB"> - {item.description} - </div> + <div + className="w-[170px] min-w-0 flex-1 truncate text-black font-B01-SB" + title={item.description} + > + {item.description} + </div>
49-51: ๋ฆฌ์คํธ ํ ์ ์ฒด ํด๋ฆญ ๋ค๋น๊ฒ์ด์ โ ๋ผ์ฐํธ ํ์ธ ์๋ฃ ๋ฐ ์ ๊ทผ์ฑ ๋ณด์ ๊ถ์ฅ
navigate(/otherslist/${item.id})ํธ์ถ์src/route/Router.tsx:72์path="/otherslist/:todoGroupId"๊ฒฝ๋ก์ ์ผ์นํจ์ ํ์ธํ์ต๋๋ค. div ๋์<Link>/<button>์ฌ์ฉ ๋๋role="button"+onKeyDown(Enter/Space)ํธ๋ค๋ฌ๋ฅผ ์ถ๊ฐํด ํค๋ณด๋ ์ ๊ทผ์ฑ์ ๋ณด์ํ์ธ์.src/pages/home/components/HomeRecruit.tsx (4)
14-33: ๋ก์ปฌ RecruitData ์ค๋ณต ์ ์ โ ๊ณต์ฉ DTO๋ก ํตํฉ ์ ์API ์๋ต ํํ๋ฅผ ์ปดํฌ๋ํธ ๋ด๋ถ์์ ์ฌ์ ์ํ๋ฉด ๋๋ฆฌํํธ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ์ต๋๋ค. validation/recruit ํน์ hook ๋จ์ ํ์ /์คํค๋ง๋ก ์น๊ฒฉํ์ฌ ๋จ์ผ ์ถ์ฒ(SOT)๋ก ๊ด๋ฆฌํ์ธ์.
์ํ์๋ฉด ๊ธฐ์กด ์คํค๋ง(RecruitItem)์ ๋งคํ๋๋ DTO ํ์ ํ์ผ๋ก ๋ถ๋ฆฌํ๋ PR ๋ณด์กฐ ํจ์น๋ฅผ ๋๋ฆด ์ ์์ต๋๋ค.
39-41: ๋ชจ๋ฌ ์ข ๋ฃ ์ ์ ํ ์ํ ์ ๋ฆฌ๋ซ๊ธฐ ์ selectedCard๋ null๋ก ๋ฆฌ์ ํ๋ฉด ๋ฉ๋ชจ๋ฆฌ/์ํ ์ผ๊ด์ฑ์ด ์ข์์ง๋๋ค.
- onClose={() => { + onClose={() => { console.log('Closing modal'); setIsModalOpen(false); + setSelectedCard(null); }}
73-80: ๋๋ฒ๊ทธ ๋ก๊ทธ ์ ๊ฑฐ ์ ์๋จ์์๋ console.log๋ ๋ฐฐํฌ ์ ์ ์ ๋ฆฌํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
- console.log('Card clicked:', data); const recruitItem = convertToRecruitItem(data); - console.log('Converted item:', recruitItem); setSelectedCard(recruitItem); setIsModalOpen(true); - console.log('Modal should open');
160-170: ๋ชจ๋ฌ ์ ๊ทผ์ฑ ๊ฐ์ (ํฌ์ปค์ค ํธ๋ฉ/์คํฌ๋กค ๋ฝ)์ค๋ฒ๋ ์ด๋ ์ ๋์ํฉ๋๋ค. ๋ค๋ง ํฌ์ปค์ค ํธ๋ฉ, ESC ๋ซ๊ธฐ, body ์คํฌ๋กค ๋ฝ์ ์ถ๊ฐํ๋ฉด ์ ๊ทผ์ฑ์ด ๊ฐ์ ๋ฉ๋๋ค. Modal ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฌ์ฉํ๋ค๋ฉด ๊ทธ ๋ด๋ถ์์ ์ฒ๋ฆฌํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
src/pages/otherTodoList/components/OtherTodoCard.tsx (4)
111-114: ๋ฆฌ์คํธ key์ index ์ฌ์ฉ ์ง์item.todoId์ฒ๋ผ ์์ ์ ์ธ ๊ณ ์ ๊ฐ์ key๋ก ์ฌ์ฉํ์ธ์. ์ฌ์ ๋ ฌ/์ถ๊ฐ/์ญ์ ์ ์๊ธฐ์น ๋ชปํ ์ฌ์ฌ์ฉ์ ๋ฐฉ์งํฉ๋๋ค.
- <li - key={index} + <li + key={item.todoId}
145-155: ๋ณ๊ฒฝ ์ค ๋นํ์ฑํ ๋ฐ ์๊ฐ์ ์ํ ์ ๊ณต์์ฒญ ์ค์๋ ๋ฒํผ์ ๋นํ์ฑํํ์ฌ ์ค๋ณต ํด๋ฆญ์ ๋ฐฉ์งํ์ธ์. aria-busy๋ก ์ํ ์ ๋ฌ๋ ๊ฐ๋ฅํฉ๋๋ค.
- <button + <button type="button" - onClick={() => toggleAdd(item.todoId, isAdded)} + onClick={() => toggleAdd(item.todoId, isAdded)} + disabled={isMutating} className={ isAdded - ? 'p-2 text-purple-500 font-B03-SB' - : 'flex items-center justify-center rounded-[10px] bg-purple-500 p-2 text-purple-50 font-B03-SB' + ? 'p-2 text-purple-500 opacity-80 disabled:opacity-50 font-B03-SB' + : 'flex items-center justify-center rounded-[10px] bg-purple-500 p-2 text-purple-50 disabled:opacity-50 font-B03-SB' } >
137-144: saveCount ์ฆ์ ๋ฐ์ ์ฌ๋ถ ํ์ธinvalidate๋ก ์๋ก๊ณ ์นจ๋๋ฉด OK์ ๋๋ค. ์ค์๊ฐ ๋ฐ์์ด ํ์ํ๋ฉด ๋๊ด์ ์ ๋ฐ์ดํธ๋ก saveCount๋ฅผ +1/-1 ์กฐ์ ํ๋ ๋ฐฉ๋ฒ๋ ์์ต๋๋ค.
162-170: ํ ์คํธ ์ ๊ทผ์ฑ(์คํฌ๋ฆฐ๋ฆฌ๋) ๊ฐ์aria-live="polite"์ role="status"๋ฅผ ๋ถ์ฌํ๋ฉด ํ๋ฉด ์ฝ๊ธฐ ํ๋ก๊ทธ๋จ์์ ์ํ ๋ณํ๋ฅผ ์ธ์งํ ์ ์์ต๋๋ค.
- <div className="fixed inset-0 z-50 flex items-center justify-center"> + <div + className="fixed inset-0 z-50 flex items-center justify-center" + role="status" + aria-live="polite" + >
๐ Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (7)
src/common/CheckList.tsx(1 hunks)src/hook/useHomeQuery.ts(1 hunks)src/pages/community/components/CommunityLeftSide.tsx(2 hunks)src/pages/home/components/HomeDreamer.tsx(1 hunks)src/pages/home/components/HomeRecruit.tsx(3 hunks)src/pages/otherTodoList/components/OtherTodoCard.tsx(3 hunks)src/validation/home/popularSchema.ts(1 hunks)
๐งฐ Additional context used
๐งฌ Code graph analysis (2)
src/pages/home/components/HomeRecruit.tsx (2)
src/store/useUserStore.ts (1)
useUserStore(19-36)src/store/filterStore.ts (1)
useFilterStore(37-66)
src/pages/otherTodoList/components/OtherTodoCard.tsx (2)
src/hook/community/useCommunityAddTodoMutation.ts (1)
useCommunityAddTodoMutation(16-51)src/hook/community/useDeleteCommunityTodos.ts (1)
useDeleteCommunityTodosMutation(8-38)
๐ Additional comments (5)
src/pages/community/components/CommunityLeftSide.tsx (1)
8-9: ์ด๋ฏธ์ง ํด๋ฐฑ ์ ์ฉ LGTMBaseImage ํด๋ฐฑ ์ถ๊ฐ๋ NPE/๋น URL ์ผ์ด์ค์ ์์ ํฉ๋๋ค.
src/pages/home/components/HomeRecruit.tsx (2)
11-13: CardDetail + RecruitItem ์ฐ๋ ๋์ LGTM์์ธ ๋ชจ๋ฌ๋ก์ ์ ํ ๊ตฌ์กฐ๊ฐ ๋ช ํํ๋ฉฐ, ํ์ ๊ธฐ๋ฐ์ผ๋ก ์ฐ๊ฒฐํ ์ ์ด ์ข์ต๋๋ค.
126-134: ์ข์์ ์์ญ์ ์ด๋ฒคํธ ์ ํ ์ฐจ๋จ LGTM์นด๋ ํด๋ฆญ๊ณผ ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํ๋๋ก stopPropagation ์ฒ๋ฆฌํ ์ ์ข์ต๋๋ค.
src/pages/otherTodoList/components/OtherTodoCard.tsx (2)
6-11: ๋ถ๋งํฌ/ํ ์คํธ ๋์ LGTMUI ํผ๋๋ฐฑ ์์(์์ด์ฝ/ํ ์คํธ) ์ถ๊ฐ๊ฐ ์ฌ์ฉ์ ๊ฒฝํ์ ๋์์ด ๋ฉ๋๋ค.
17-19: saveCount/isSaved ํ๋ ๋์ โ ๋ฐ์ดํฐ ์์ฒ ํ์ธ๋ ํ๋๊ฐ API์์ ํญ์ ๋ด๋ ค์ค๋์ง(ํน์ ์ ํ์ ) ํ์ธ ๋ถํ๋๋ฆฝ๋๋ค. ๋๋ฝ ๊ฐ๋ฅ์ฑ์ด ์์ผ๋ฉด ์์ ์คํค๋ง/ํ์ ๋ optional๋ก ๋ง์ถ๊ณ ์์ ํ ๊ธฐ๋ณธ๊ฐ์ ์ ์งํ์ธ์.
| const convertToRecruitItem = (data: RecruitData): RecruitItem => { | ||
| return { | ||
| url: data.url || '', | ||
| active: data.active || 1, | ||
| title: data.title || '', | ||
| jobName: data.jobName || '', | ||
| companyName: data.companyName || '', | ||
| locationName: data.locationName || null, | ||
| jobTypeName: data.jobTypeName || '', | ||
| experienceLevel: data.experienceLevel || '', | ||
| requiredEducationLevel: data.requiredEducationLevel || '', | ||
| closeType: data.closeType || '', | ||
| salary: data.salary || '', | ||
| id: String(data.id), | ||
| postTimestamp: data.postTimestamp || '', | ||
| postDate: data.postDate || '', | ||
| 'expiration-timestamp': data['expiration-timestamp'] || '', | ||
| 'expiration-date': data['expiration-date'] || '', | ||
| deadline: data.deadline || '', | ||
| count: | ||
| typeof data.count === 'string' ? parseInt(data.count) : data.count || 0, | ||
| }; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํ์ฑ ์ํ(active) 0์ด 1๋ก ์ค์ธ๋ ์ ์์ + ์ ์ ํ์ฑ radix ๋ช ์
- active: data.active || 1์ 0(๋นํ์ฑ)์ 1๋ก ๋ฎ์ด์จ ๋ฒ๋ฆฝ๋๋ค. nullish ๋ณํฉ(??)์ ์ฐ์ธ์.
- count: parseInt ์ฌ์ฉ ์ 10์ง์ radix๋ฅผ ๋ช ์ํ์ธ์.
์ ์ฉ ์ ์:
const convertToRecruitItem = (data: RecruitData): RecruitItem => {
return {
url: data.url || '',
- active: data.active || 1,
+ active: data.active ?? 1,
title: data.title || '',
jobName: data.jobName || '',
companyName: data.companyName || '',
locationName: data.locationName || null,
jobTypeName: data.jobTypeName || '',
experienceLevel: data.experienceLevel || '',
requiredEducationLevel: data.requiredEducationLevel || '',
closeType: data.closeType || '',
salary: data.salary || '',
id: String(data.id),
postTimestamp: data.postTimestamp || '',
postDate: data.postDate || '',
'expiration-timestamp': data['expiration-timestamp'] || '',
'expiration-date': data['expiration-date'] || '',
deadline: data.deadline || '',
count:
- typeof data.count === 'string' ? parseInt(data.count) : data.count || 0,
+ typeof data.count === 'string'
+ ? parseInt(data.count, 10)
+ : (data.count ?? 0),
};
};๐ Committable suggestion
โผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const convertToRecruitItem = (data: RecruitData): RecruitItem => { | |
| return { | |
| url: data.url || '', | |
| active: data.active || 1, | |
| title: data.title || '', | |
| jobName: data.jobName || '', | |
| companyName: data.companyName || '', | |
| locationName: data.locationName || null, | |
| jobTypeName: data.jobTypeName || '', | |
| experienceLevel: data.experienceLevel || '', | |
| requiredEducationLevel: data.requiredEducationLevel || '', | |
| closeType: data.closeType || '', | |
| salary: data.salary || '', | |
| id: String(data.id), | |
| postTimestamp: data.postTimestamp || '', | |
| postDate: data.postDate || '', | |
| 'expiration-timestamp': data['expiration-timestamp'] || '', | |
| 'expiration-date': data['expiration-date'] || '', | |
| deadline: data.deadline || '', | |
| count: | |
| typeof data.count === 'string' ? parseInt(data.count) : data.count || 0, | |
| }; | |
| }; | |
| const convertToRecruitItem = (data: RecruitData): RecruitItem => { | |
| return { | |
| url: data.url || '', | |
| active: data.active ?? 1, | |
| title: data.title || '', | |
| jobName: data.jobName || '', | |
| companyName: data.companyName || '', | |
| locationName: data.locationName || null, | |
| jobTypeName: data.jobTypeName || '', | |
| experienceLevel: data.experienceLevel || '', | |
| requiredEducationLevel: data.requiredEducationLevel || '', | |
| closeType: data.closeType || '', | |
| salary: data.salary || '', | |
| id: String(data.id), | |
| postTimestamp: data.postTimestamp || '', | |
| postDate: data.postDate || '', | |
| 'expiration-timestamp': data['expiration-timestamp'] || '', | |
| 'expiration-date': data['expiration-date'] || '', | |
| deadline: data.deadline || '', | |
| count: | |
| typeof data.count === 'string' | |
| ? parseInt(data.count, 10) | |
| : (data.count ?? 0), | |
| }; | |
| }; |
๐ค Prompt for AI Agents
In src/pages/home/components/HomeRecruit.tsx around lines 49 to 71, the
converter incorrectly treats active: data.active || 1 which turns a legitimate 0
into 1 and parseInt lacks a radix; change to use nullish coalescing for active
(active: data.active ?? 1) so only null/undefined default to 1, and when parsing
count explicitly pass a radix 10 (e.g. parseInt(data.count, 10)) while
preserving the existing fallback to 0 when count is missing or not a number.
| const toggleAdd = (id: number, isAdded: boolean) => { | ||
| if (isAdded) { | ||
| deleteTodoMutation.mutate( | ||
| { id }, | ||
| { | ||
| onSuccess: () => { | ||
| setAdded((prev) => ({ ...prev, [id]: false })); | ||
| setToastMessage('ํ ์ผ์ด ์ทจ์๋์์ต๋๋ค.'); | ||
| setShowToast(true); | ||
| setTimeout(() => setShowToast(false), 2500); | ||
| }, | ||
| onError: () => { | ||
| alert('์ถ๊ฐ ์ทจ์์ ์คํจํ์ด์.'); | ||
| }, | ||
| } | ||
| ); | ||
| } else { | ||
| addTodoMutation.mutate( | ||
| { id }, | ||
| { | ||
| onSuccess: () => { | ||
| setShowToast(true); | ||
| setAdded((prev) => ({ ...prev, [id]: true })); | ||
| setToastMessage('ํ ์ผ์ด ์ถ๊ฐ๋์์ต๋๋ค.'); | ||
| setTimeout(() => { | ||
| setShowToast(false); | ||
| }, 2500); | ||
| }, | ||
| onError: () => { | ||
| alert('๋ด ํ ์ผ ์ถ๊ฐ์ ์คํจํ์ด์.'); | ||
| }, | ||
| } | ||
| ); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion
๋น ๋ฅธ ์ฐํ/์ค๋ณต ์์ฒญ ๋ฐฉ์ง ๋ฐ ํ์์์ ๊ด๋ฆฌ
์ฐ์ ํด๋ฆญ ์ add/delete๊ฐ ์ค์ฒฉ ํธ์ถ๋ ์ ์์ต๋๋ค. ๊ฐ mutation์ isPending ์ํ๋ก ๋ฒํผ์ ๋นํ์ฑํํ๊ณ , setTimeout์ ํด๋ฆฐ์ ์ผ๋ก ํด์ ํ์ธ์.
๋ค์ ํจ์น ์์:
- const addTodoMutation = useCommunityAddTodoMutation();
- const deleteTodoMutation = useDeleteCommunityTodosMutation();
+ const {
+ mutate: addMutate,
+ isPending: isAdding,
+ } = useCommunityAddTodoMutation();
+ const {
+ mutate: deleteMutate,
+ isPending: isDeleting,
+ } = useDeleteCommunityTodosMutation();
+ const isMutating = isAdding || isDeleting;
+ const toastTimers = useRef<number[]>([]);
...
- deleteTodoMutation.mutate(
+ deleteMutate(
{ id },
{
onSuccess: () => {
setAdded((prev) => ({ ...prev, [id]: false }));
setToastMessage('ํ ์ผ์ด ์ทจ์๋์์ต๋๋ค.');
setShowToast(true);
- setTimeout(() => setShowToast(false), 2500);
+ const t = window.setTimeout(() => setShowToast(false), 2500);
+ toastTimers.current.push(t);
},
...
- addTodoMutation.mutate(
+ addMutate(
{ id },
{
onSuccess: () => {
setShowToast(true);
setAdded((prev) => ({ ...prev, [id]: true }));
setToastMessage('ํ ์ผ์ด ์ถ๊ฐ๋์์ต๋๋ค.');
- setTimeout(() => {
- setShowToast(false);
- }, 2500);
+ const t = window.setTimeout(() => setShowToast(false), 2500);
+ toastTimers.current.push(t);
},
...
+ useEffect(() => {
+ return () => {
+ toastTimers.current.forEach((t) => clearTimeout(t));
+ toastTimers.current = [];
+ };
+ }, []);๋ฒํผ ๋นํ์ฑํ๋ ํ๋จ ๋ฒํผ ์์ญ์์ isMutating์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค(์๋ ์ฝ๋ฉํธ ์ฐธ๊ณ ).
๐ Committable suggestion
โผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const toggleAdd = (id: number, isAdded: boolean) => { | |
| if (isAdded) { | |
| deleteTodoMutation.mutate( | |
| { id }, | |
| { | |
| onSuccess: () => { | |
| setAdded((prev) => ({ ...prev, [id]: false })); | |
| setToastMessage('ํ ์ผ์ด ์ทจ์๋์์ต๋๋ค.'); | |
| setShowToast(true); | |
| setTimeout(() => setShowToast(false), 2500); | |
| }, | |
| onError: () => { | |
| alert('์ถ๊ฐ ์ทจ์์ ์คํจํ์ด์.'); | |
| }, | |
| } | |
| ); | |
| } else { | |
| addTodoMutation.mutate( | |
| { id }, | |
| { | |
| onSuccess: () => { | |
| setShowToast(true); | |
| setAdded((prev) => ({ ...prev, [id]: true })); | |
| setToastMessage('ํ ์ผ์ด ์ถ๊ฐ๋์์ต๋๋ค.'); | |
| setTimeout(() => { | |
| setShowToast(false); | |
| }, 2500); | |
| }, | |
| onError: () => { | |
| alert('๋ด ํ ์ผ ์ถ๊ฐ์ ์คํจํ์ด์.'); | |
| }, | |
| } | |
| ); | |
| } | |
| }; | |
| // Destructure mutate + pending states, track combined mutating status and toast timers | |
| const { | |
| mutate: addMutate, | |
| isPending: isAdding, | |
| } = useCommunityAddTodoMutation(); | |
| const { | |
| mutate: deleteMutate, | |
| isPending: isDeleting, | |
| } = useDeleteCommunityTodosMutation(); | |
| const isMutating = isAdding || isDeleting; | |
| const toastTimers = useRef<number[]>([]); | |
| const toggleAdd = (id: number, isAdded: boolean) => { | |
| if (isAdded) { | |
| deleteMutate( | |
| { id }, | |
| { | |
| onSuccess: () => { | |
| setAdded((prev) => ({ ...prev, [id]: false })); | |
| setToastMessage('ํ ์ผ์ด ์ทจ์๋์์ต๋๋ค.'); | |
| setShowToast(true); | |
| const t = window.setTimeout(() => setShowToast(false), 2500); | |
| toastTimers.current.push(t); | |
| }, | |
| onError: () => { | |
| alert('์ถ๊ฐ ์ทจ์์ ์คํจํ์ด์.'); | |
| }, | |
| } | |
| ); | |
| } else { | |
| addMutate( | |
| { id }, | |
| { | |
| onSuccess: () => { | |
| setShowToast(true); | |
| setAdded((prev) => ({ ...prev, [id]: true })); | |
| setToastMessage('ํ ์ผ์ด ์ถ๊ฐ๋์์ต๋๋ค.'); | |
| const t = window.setTimeout(() => setShowToast(false), 2500); | |
| toastTimers.current.push(t); | |
| }, | |
| onError: () => { | |
| alert('๋ด ํ ์ผ ์ถ๊ฐ์ ์คํจํ์ด์.'); | |
| }, | |
| } | |
| ); | |
| } | |
| }; | |
| useEffect(() => { | |
| return () => { | |
| toastTimers.current.forEach((t) => clearTimeout(t)); | |
| toastTimers.current = []; | |
| }; | |
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
๐งน Nitpick comments (3)
src/hook/community/query/useGetHotPopularQuery.ts (3)
14-15: todoGroupId ํ์ ์ถ๊ฐ โ API ์คํค๋ง/์๋น์ฒ ์ ํฉ์ฑ ํ์ธ ๊ถ์ฅํ์ฌ queryFn์์ ๋ฐํ์ ๊ฒ์ฆ ์์ด ์๋ฒ ์๋ต์ ์ ๋ขฐํ๋ฏ๋ก, ์๋ฒ๊ฐ
todoGroupId๋ฅผ ๋๋ฝํ๋ฉดundefined๊ฐ UI๋ก ํ๋ฌ๊ฐ ์ ์์ต๋๋ค. ๋, ์ปค๋ฎค๋ํฐ HOT ๋ฆฌ์คํธ๋ ์ฌ์ ํitem.id๋ก ๋ค๋น๊ฒ์ด์ ํ๋ค๊ณ ํ๋,todoGroupId์ ์ค์ ์ฌ์ฉ ๋ชฉ์ (๊ทธ๋ฃน ID vs ์์ดํ ID)์ ํ์ธํด ์ฃผ์ธ์. ์คํค๋ง๊ฐ ์์ง ํ์ ๋์ง ์์๋ค๋ฉด ์์๋ก optional ์ฒ๋ฆฌํ๊ฑฐ๋ ๋ฐํ์ ํ์ฑ(์: Zod)์ผ๋ก ๋ณด์ฅํ๋ ๋ฐฉ์์ ๊ถ์ฅํฉ๋๋ค.์ ํ์ง A(์์ ์์ถฉ):
- todoGroupId: number; + todoGroupId?: number;์ํ์๋ฉด Zod ์คํค๋ง/ํ์ฑ ์ฝ๋๋ ์ ์ํด ๋๋ฆด๊ฒ์.
26-35: ์๋ต ์ ๋ค๋ฆญ/๊ฒ์ฆ ์ ์ฉ์ผ๋ก ์นจ๋ฌต ์คํจ ๋ฐฉ์ง๋น์ ์ ์๋ต ์ ๋น ๋ฐฐ์ด์ ๋ฐํํ๋ฉด ์ฅ์ ๊ฐ ๊ฐ๋ ค์ง ์ ์์ต๋๋ค. Axios ์ ๋ค๋ฆญ๊ณผ ๊ฐ๋จํ ํํ ๊ฒ์ฆ์ผ๋ก ์๋ฌ๋ฅผ ๋ช ์์ ์ผ๋ก ํฐ๋จ๋ ค React Query์ ์๋ฌ ํ๋ก์ฐ๋ฅผ ํ๋๋ก ํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
- return useQuery<HotPopularItem[]>({ + return useQuery<HotPopularItem[], Error>({ queryKey: ['hotPopular', selectedJobName], enabled: !!selectedJobName, queryFn: async () => { - const res = await api.get('/v1/community/todos/popular', { + const { data } = await api.get<HotPopularApiResponse>('/v1/community/todos/popular', { params: { jobName: selectedJobName }, }); - const body = res.data as HotPopularApiResponse; - return Array.isArray(body?.data) ? body.data : []; + if (!Array.isArray(data?.data)) { + throw new Error('Invalid response shape: /v1/community/todos/popular'); + } + return data.data; }, staleTime: 1000 * 60 * 5, refetchOnWindowFocus: false, });
5-10: imageUrl null ๊ฐ๋ฅ์ฑ ํ์ธ ๋ฐ ํ์ ์ ํฉ์ฑ๊ด๋ จ ์คํค๋ง์์ ํ๋กํ ์ด๋ฏธ์ง๊ฐ nullable๋ก ๋ฐ๋ ๋งฅ๋ฝ์ด ์์ต๋๋ค. ์ด ๋ฐ์ดํฐ ์์ค๊ฐ ๋์ผํ๋ค๋ฉด
imageUrl๋ null์ด ๋ค์ด์ฌ ์ ์์ผ๋ ํ์ /์๋น์ฒ ์ ํฉ์ฑ์ ํ์ธํด ์ฃผ์ธ์. ํ์ ์ ๋ค์๊ณผ ๊ฐ์ด ํ์ฅํ๊ฑฐ๋, ์๋น ์ธก์์ ํด๋ฐฑ์ ์ ์ฉํ์ธ์.- imageUrl: string; + imageUrl: string | null;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and canโt be posted inline due to platform limitations.
โ ๏ธ Outside diff range comments (1)
src/pages/community/components/CommunityContents.tsx (1)
52-87: ์ค๋ณต ์์ฒญ ๋ฐฉ์ง: isPending์ผ๋ก ๋ฒํผ ๋นํ์ฑํ ๋ฐ ํ ์คํธ ํ์ด๋จธ ํด๋ฆฐ์์ฐ์ ํด๋ฆญ ์ add/delete๊ฐ ์ค๋ณต ํธ์ถ๋ ์ ์์ต๋๋ค. ๊ฐ mutation์
isPending์ ์ด์ฉํด ๋ฒํผ์ ๋นํ์ฑํํ๊ณ ,setTimeout์ ref๋ก ๊ด๋ฆฌํด ํด๋ฆฐ์ ํ์ธ์.์์ ํจ์น:
- const addTodoMutation = useCommunityAddTodoMutation(); - const deleteTodoMutation = useDeleteCommunityTodosMutation(); + const { mutate: addMutate, isPending: isAdding } = useCommunityAddTodoMutation(); + const { mutate: deleteMutate, isPending: isDeleting } = useDeleteCommunityTodosMutation(); + const isMutating = isAdding || isDeleting; + const toastTimerRef = useRef<number | null>(null); ... - deleteTodoMutation.mutate( + deleteMutate( ... - addTodoMutation.mutate( + addMutate( ... - setTimeout(() => setShowToast(false), 2500); + toastTimerRef.current = window.setTimeout(() => setShowToast(false), 2500); ... + useEffect(() => () => { + if (toastTimerRef.current) clearTimeout(toastTimerRef.current); + }, []);๋ฒํผ ๋นํ์ฑํ:
- <button + <button type="button" - onClick={() => toggleAdd(post.id, isAdded, post.description)} + onClick={() => toggleAdd(post.id, isAdded, post.description)} + disabled={isMutating}
โป๏ธ Duplicate comments (2)
src/pages/jobDetail/components/TabContent/TreeContents.tsx (1)
29-31: ํ์ด๋จธ ์ ๋ฆฌ ๋๋ฝ ๊ฐ๋ฅ์ฑ์ธ๋ง์ดํธ ์ ํ์ด๋จธ ํด๋ฆฐ์ ์ ์ถ๊ฐํ์ธ์. Sprout ์ฝ๋ฉํธ์ ๋์ผ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌ ๊ถ์ฅ.
src/pages/otherTodoList/components/OtherTodoCard.tsx (1)
37-72: ์ค๋ณต ์์ฒญ ๋ฐฉ์ง ๋ฐ ํ์์์ ํด๋ฆฐ์์ด์ ์ฝ๋ฉํธ์ ๋์ผํ๊ฒ isPending ๊ธฐ๋ฐ ๋นํ์ฑํ์ ํ์ด๋จธ ํด๋ฆฐ์ ์ ์ ์ฉํ์ธ์. ์ฐ์ ํด๋ฆญ ์ ์๋ฒ์ ์ค๋ณต ์์ฒญ์ด ๊ฐ ์ ์์ต๋๋ค.
๐งน Nitpick comments (15)
src/utils/amplitude.ts (6)
9-11: SSR ์์ ์ฑ ๋ฐ ์ ์ญ ์ฐธ์กฐ ์ผ๊ดํ: window.location ์ฌ์ฉ ๊ถ์ฅ
location.pathname๋์window.location.pathname์ ์ฌ์ฉํด ์ ์ญ ์ฐธ์กฐ๋ฅผ ๋ช ์์ ์ผ๋ก ํ๊ณ , SSR ๊ฐ๋์์ ์ผ๊ด์ฑ์ ๋ง์ถ์ธ์.- const currentPath = location.pathname; + const currentPath = window.location.pathname;
12-21: source_page ๊ฐ ํ์คํ ๋ฐ ๋๋ฝ ๋ผ์ฐํธ ๋ณด์Sprout/Tree ์ปดํฌ๋ํธ๋ ๋ณดํต jobDetail ๊ฒฝ๋ก์์ ํธ์ถ๋ฉ๋๋ค. ํ์ฌ ๋งคํ์ '/jobinfo'๋ง ์ปค๋ฒํ์ฌ ๋์ผ ๊ธฐ๋ฅ์ด ํ์ด์ง์ ๋ฐ๋ผ 'jobinfo/' vs '/jobDetail/...'๋ก ์์ฌ ๊ธฐ๋ก๋ ์ ์์ต๋๋ค. ์ธก์ ์ผ๊ด์ฑ์ ์ํด jobDetail๋ ๋ช ์ ๋งคํํ๊ณ , ์ฌ๋์/๋์๋ฌธ์ ๊ท์น์ ํต์ผํ์ธ์.
- if (currentPath.includes('/community')) { + if (currentPath.includes('/community')) { sourcePage = 'community/'; - } else if (currentPath.includes('/jobinfo')) { + } else if (currentPath.includes('/jobinfo')) { sourcePage = 'jobinfo/'; + } else if (currentPath.includes('/jobdetail')) { + sourcePage = 'jobdetail/'; } else if (currentPath.includes('/otherslist')) { sourcePage = 'otherslist/'; } else { sourcePage = currentPath; }
23-28: ์ด๋ฒคํธ ์๋งจํฑ ๋ถ์ผ์น: source_method 'copy_btn' โ ์ค์ ๋ ์ถ๊ฐ ๋ฒํผํ์ฌ ์ด๋ฒคํธ๋ '๋ด ํ ์ผ์ ์ถ๊ฐ' ํ๋ฆ์ธ๋ฐ
source_method๊ฐcopy_btn์ผ๋ก ๊ธฐ๋ก๋ฉ๋๋ค. ๋ถ์ ํผ์ ์ ํผํ๋ ค๋ฉด ๋ฉ์๋ ๊ฐ์ ๊ต์ ํ๊ฑฐ๋, ํด๋ฆญ/์ฑ๊ณต ์ด๋ฒคํธ๋ฅผ ๊ตฌ๋ถํด ๊ธฐ๋กํ์ธ์.- source_method: 'copy_btn', + source_method: 'add_btn',๋์: ํด๋ฆญ ์ 'todo_import_click', ์ฑ๊ณต ์ 'todo_import_success'๋ฅผ ๋ฐ๋ก ๋ณด๋ด๋ ํจํด์ผ๋ก ๋ถ๋ฆฌ.
23-28: ๋ถ์ ์๋ณ์ ํ์ฅ: todo_id ์ ๋ฌ ๊ณ ๋ ค๊ธธ์ด(
todo_length)๋ง์ผ๋ก๋ ํญ๋ชฉ ์๋ณ์ด ๋ถ๊ฐ๋ฅํฉ๋๋ค. ๊ฐ์ธ์ ๋ณด ๋ ธ์ถ์ ํผํ๋ฉด์๋ ๋ถ์ ํ์ง์ ๋์ด๋ ค๋ฉด ID๋ฅผ ์ถ๊ฐ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํ๋ ํํ๋ก ์ ํธ ์๊ทธ๋์ฒ๋ฅผ ํ์ฅํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.์:
-export const trackTodoImport = (todoTitle: string) => { +export const trackTodoImport = (todoTitle: string, options?: { todoId?: number }) => { ... - window.amplitude.track('todo_import', { + window.amplitude.track('todo_import', { source_method: 'add_btn', source_page: sourcePage, todo_length: todoTitle.length, + todo_id: options?.todoId, timestamp: new Date().toISOString(), });ํธ์ถ๋ถ์์ ID๊ฐ ์๋ ๊ฒฝ์ฐ ํจ๊ป ์ ๋ฌ.
29-30: prod ์ฝ์ ๋ ธ์ด์ฆ ์ ๊ฑฐํ๋ก๋์ ์์ ๋ถํ์ํ ์ฝ์ ์ถ๋ ฅ์ ์ง์ํ์ธ์. ๋น๋ ํ๊ฒฝ ๋ณ์๋ก ๊ฐ๋ํ๊ฑฐ๋ ๋ก๊ทธ๋ฅผ ์ ๊ฑฐํด ์ฃผ์ธ์.
- console.log('Amplitude event sent: todo_import'); + if (process.env.NODE_ENV !== 'production') { + console.log('Amplitude event sent: todo_import'); + }
3-8: window.amplitude ํ์ ์ ์ธ ๋๋ฝ ๊ฐ๋ฅ์ฑ
window.amplitude๋ ๊ธฐ๋ณธ DOM ํ์ ์ ์์ด์ TS ์๋ฌ๊ฐ ๋ ์ ์์ต๋๋ค. ๊ธ๋ก๋ฒ ์ ์ธ์ ์ถ๊ฐํด ํ์ ์ ๋ช ์ํ์ธ์.์๋๋ฅผ ๋ณธ ํ์ผ ์ต์๋จ(๋๋ .d.ts) ์ด๋๊ฐ์ ์ถ๊ฐ:
declare global { interface Window { amplitude?: { track: (event: string, props?: Record<string, any>) => void }; } } export {};src/pages/jobDetail/components/TabContent/SproutContent.tsx (3)
21-33: ์ด๋ฒคํธ๋ ์ฑ๊ณต ์์ ์ ๊ธฐ๋กํ์ฌ ํด๋ฆญ ์งํ์ ์ถ์ ํ๊ณ ์์ด ์คํจ ์ผ์ด์ค๋ ์ฑ๊ณต์ผ๋ก ์ง๊ณ๋ฉ๋๋ค. onSuccess์์ ํธ๋ํนํ๋๋ก ์ด๋ํ๊ฑฐ๋, ํด๋ฆญ/์ฑ๊ณต์ ๋ถ๋ฆฌ ์ด๋ฒคํธ๋ก ๊ธฐ๋กํ์ธ์.
- const handleAdd = (jobTodoId: number, title: string) => { + const handleAdd = (jobTodoId: number, title: string) => { if (completedId.has(jobTodoId)) return; setClickedId(jobTodoId); - trackTodoImport(title); // Amplitude ์ด๋ฒคํธ ํธ๋ํน mutate(jobTodoId, { onSuccess: () => { + trackTodoImport(title); // ์ฑ๊ณต ์์ ํธ๋ํน setCompletedId((prev) => new Set(prev).add(jobTodoId)); setShowToast(true); setTimeout(() => setShowToast(false), 2000); }, onSettled: () => setClickedId(null), }); };
29-31: ํ์ด๋จธ ์ ๋ฆฌ ๋๋ฝ ๊ฐ๋ฅ์ฑ์ธ๋ง์ดํธ ์
setTimeoutํด๋ฆฐ์ ์ด ์์ด ๋ฉ๋ชจ๋ฆฌ/๊ฒฝ๊ณ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค. ref๋ก ํ์ด๋จธ๋ฅผ ๋ณด๊ดํ๊ณ useEffect cleanup์์ ์ ๋ฆฌํ์ธ์.์ถ๊ฐ ์ฝ๋(์ปดํฌ๋ํธ ์๋จ์):
const toastTimerRef = useRef<number | null>(null);๋ณ๊ฒฝ:
- setTimeout(() => setShowToast(false), 2000); + toastTimerRef.current = window.setTimeout(() => setShowToast(false), 2000);ํด๋ฆฐ์ :
useEffect(() => () => { if (toastTimerRef.current) clearTimeout(toastTimerRef.current); }, []);
8-11: ๋ฏธ์ฌ์ฉ prop ์ ๋ฆฌ
SproutContentProps์jobId๊ฐ ์ฌ์ฉ๋์ง ์์ต๋๋ค. ๋ถํ์ํ prop์ ์ ๊ฑฐํด ์ธํฐํ์ด์ค๋ฅผ ๋จ์ํํ์ธ์.src/pages/jobDetail/components/TabContent/TreeContents.tsx (1)
21-33: ์ด๋ฒคํธ๋ ์ฑ๊ณต ์์ ์ ๊ธฐ๋กSprout๊ณผ ๋์ผํ๊ฒ ์ฑ๊ณต ์์ ์ ๊ธฐ๋กํ๋๋ก ์ด๋ํ์ธ์.
- trackTodoImport(title); // Amplitude ์ด๋ฒคํธ ํธ๋ํน mutate(jobTodoId, { onSuccess: () => { + trackTodoImport(title); // ์ฑ๊ณต ์์ ํธ๋ํน setCompletedId((prev) => new Set(prev).add(jobTodoId)); setShowToast(true); setTimeout(() => setShowToast(false), 2000); },src/pages/community/components/CommunityContents.tsx (2)
69-70: ์ด๋ฒคํธ ์์ ์กฐ์ ๋๋ ์ด๋ฒคํธ๋ช ๋ถ๋ฆฌ์ถ๊ฐ ์ฑ๊ณต ์ ํธ๋ํน๋์ด ์คํจ ๊ฑด๋ ์ฑ๊ณต์ผ๋ก ์ง๊ณ๋ฉ๋๋ค. onSuccess์์ ํธ์ถํ๊ฑฐ๋, ํด๋ฆญ/์ฑ๊ณต ์ด๋ฒคํธ๋ฅผ ๋ถ๋ฆฌํ์ธ์.
- trackTodoImport(todoTitle); // Amplitude ์ด๋ฒคํธ ํธ๋ํน + // onSuccess ๋ด๋ถ๋ก ์ด๋ ๊ถ์ฅ ๋๋ ๋ณ๋ 'todo_import_click' ์ด๋ฒคํธ๋ก ๋ถ๋ฆฌ
136-144: ๊ธด ๋ณธ๋ฌธ(description) ๋์ ์งง์ ์๋ณ์ ์ฌ์ฉ ๊ฒํ
todoTitle๋กpost.description์ ์ ๋ฌํ๋ฉด ๊ธธ์ด ์ธก์ ์ด ๊ณผ๋ ๊ธฐ๋ก๋ ์ ์์ต๋๋ค. ์ ๋ชฉ(post.name) ๋๋ ์๋ถ๋ถ๋ง ์ฌ์ฉํ๋๋ก ์ฌ๊ฒํ ํ์ธ์.- onClick={() => toggleAdd(post.id, isAdded, post.description)} + onClick={() => toggleAdd(post.id, isAdded, post.name)}src/pages/otherTodoList/components/OtherTodoCard.tsx (3)
149-157: ์ด๋ฒคํธ๋ ์ฑ๊ณต ์์ ์ ๊ธฐ๋ก์ฑ๊ณต/์คํจ๋ฅผ ๊ตฌ๋ถํ๋ ค๋ฉด onSuccess์์ ํธ๋ํนํ๊ฑฐ๋ ์ด๋ฒคํธ๋ฅผ ๋ถ๋ฆฌํ์ธ์.
- trackTodoImport(todoTitle); // Amplitude ์ด๋ฒคํธ ํธ๋ํน addTodoMutation.mutate( { id }, { onSuccess: () => { + trackTodoImport(todoTitle); // ์ฑ๊ณต ์์ ํธ๋ํน setShowToast(true);
114-116: ๋ฆฌ์คํธ key์ index ์ฌ์ฉ ์ง์
indexkey๋ ์ถ๊ฐ/์ญ์ ํ ๊ธ ์ ์๋ชป๋ ์ฌ์ฌ์ฉ์ ์ ๋ฐํ ์ ์์ต๋๋ค. ๊ณ ์ ID๋ฅผ ์ฌ์ฉํ์ธ์.- key={index} + key={item.todoId}
118-124: ํด๋ฆญ ๊ฐ๋ฅ UI์ ๋์ ๋ถ์ผ์น์ฒดํฌ ๋ฐ์ค ์์ญ์
cursor-pointer๊ฐ ์์ผ๋ onClick ๋์์ด ์์ต๋๋ค. UX ํผ์ ์ ํผํ๋ ค๋ฉด ํฌ์ธํฐ ์ ๊ฑฐ ๋๋ ํด๋ฆญ ๋์์ ์ถ๊ฐํ์ธ์.- className={`flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded-[8px] border ${ + className={`flex h-[30px] w-[30px] items-center justify-center rounded-[8px] border ${
๐ Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (5)
src/pages/community/components/CommunityContents.tsx(4 hunks)src/pages/jobDetail/components/TabContent/SproutContent.tsx(3 hunks)src/pages/jobDetail/components/TabContent/TreeContents.tsx(3 hunks)src/pages/otherTodoList/components/OtherTodoCard.tsx(3 hunks)src/utils/amplitude.ts(1 hunks)
๐งฐ Additional context used
๐งฌ Code graph analysis (4)
src/pages/jobDetail/components/TabContent/SproutContent.tsx (1)
src/utils/amplitude.ts (1)
trackTodoImport(3-31)
src/pages/jobDetail/components/TabContent/TreeContents.tsx (1)
src/utils/amplitude.ts (1)
trackTodoImport(3-31)
src/pages/community/components/CommunityContents.tsx (1)
src/utils/amplitude.ts (1)
trackTodoImport(3-31)
src/pages/otherTodoList/components/OtherTodoCard.tsx (3)
src/hook/community/useCommunityAddTodoMutation.ts (1)
useCommunityAddTodoMutation(16-51)src/hook/community/useDeleteCommunityTodos.ts (1)
useDeleteCommunityTodosMutation(8-38)src/utils/amplitude.ts (1)
trackTodoImport(3-31)
๐ Additional comments (1)
src/pages/otherTodoList/components/OtherTodoCard.tsx (1)
141-145: ๋ถ๋งํฌ ์นด์ดํธ NaN ๋ฐฉ์ง ์ฒ๋ฆฌ๋ OK
{item.saveCount || 0}๋ก ์์ ์ ์ผ๋ก ๋ ๋๋ง๋๋ ์ ์ข์ต๋๋ค.
Chasyuss
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํ์ธ์ด์...!
๐ ํ ๋ฆฌํ์คํธ ์ ์
๐ ์์ ๋ด์ฉ
qa์ ๋ด์ฉ ์ผ๋ถ ์์ .
๋ ธ์ ์ ์ฐธ๊ณ ํด์ฃผ์ธ์.
๐ธ ์คํฌ๋ฆฐ์ท (์ ํ ์ฌํญ)
๐ ๊ธฐํ
Summary by CodeRabbit
New Features
Style
Bug Fixes